using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using System.Threading;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
using System.Xml;
using System.Net;
using MapPointBatchGeocoder.MapPointCustomerDataServices;

namespace Geospatial
{
    /// <summary>
    /// Wraps the web service proxy for MapPoint's Customer Data Service in order to add retry capability when invoking services.  Retry is necessary in order
    /// to add resiliency in the face of minor network/Internet hiccups and dropouts.  This happens from time to time since the web service call is over the Internet.
    /// </summary>
    class CDSRetryProxy : IDisposable 
    {
        private int retryAttempts = 0;                                  // number of retry attempts to make
        private int retryDelay = 0;                                     // the delay between attempts
        private CustomerDataService cds = new CustomerDataService();    // the default web service proxy for MapPoint's CDS

        /// <summary>Represents an enumeration of all available web methods of the proxy</summary>
        private enum MapPointCommand
        {
            StartDownload,
            GetDownloadFileURL,
            GetJobState,
            DeleteDataSource,
            StartUpload,
            UploadData,
            FinishUpload
        }

        /// <summary>
        /// Initializes the CDSRetryProxy
        /// </summary>
        public CDSRetryProxy(int retryAttempts, int retryDelay)
        {
            this.retryAttempts = retryAttempts;
            this.retryDelay = retryDelay;
        }

        public string StartDownload(DownloadSpecification specification)
        {
            return (string)RunCommand(MapPointCommand.StartDownload, specification);
        }

        public string GetDownloadFileURL(string jobID)
        {
            return (string)RunCommand(MapPointCommand.GetDownloadFileURL, jobID);
        }

        public JobState GetJobState(string jobID)
        {
            return (JobState)RunCommand(MapPointCommand.GetJobState, jobID);
        }

        public string DeleteDataSource(string DataSourceName, LocationDataEnvironment Environment)
        {
            return (string)RunCommand(MapPointCommand.DeleteDataSource, DataSourceName, Environment);
        }

        public string StartUpload(UploadSpecification specification)
        {
            return (string)RunCommand(MapPointCommand.StartUpload, specification);
        }

        public long UploadData(string jobID, [System.Xml.Serialization.XmlElementAttribute(DataType = "base64Binary")] byte[] buffer, long bytesPreviouslyUploaded)
        {
            return (long)RunCommand(MapPointCommand.UploadData, jobID, buffer, bytesPreviouslyUploaded);
        }
        
        public void FinishUpload(string jobID, long totalFileSize)
        {
            RunCommand(MapPointCommand.FinishUpload, jobID, totalFileSize);
        }

        public ICredentials Credentials
        {
            set
            {
                cds.Credentials = value;
            }
        }

        public bool PreAuthenticate
        {
            set
            {
                cds.PreAuthenticate = value;
            }
        }

        /// <summary>
        /// Invokes a given web service method of the proxy, but will retry the attempt in case of failure.
        /// </summary>
        /// <param name="command">The MapPoint method to invoke</param>
        /// <param name="commandParams">A generic list of command parameters, which differs based on the command</param>
        /// <returns></returns>
        private object RunCommand(MapPointCommand command, params object[] commandParams)
        {
            // The return value of the MapPoint command.
            object returnValue = null;

            // Execute the command, but with retry capability.
            for (int retryIndex = 1; retryIndex <= this.retryAttempts; retryIndex++)
            {
                try
                {
                    // Based on the command passed in, execute the corresponding MapPoint CDS method.
                    switch (command)
                    {
                        case MapPointCommand.DeleteDataSource:
                            returnValue = cds.DeleteDataSource((string)commandParams[0], (LocationDataEnvironment)commandParams[1]);
                            break;
                        case MapPointCommand.FinishUpload:
                            cds.FinishUpload((string)commandParams[0], (long)commandParams[1]);
                            break;
                        case MapPointCommand.GetDownloadFileURL:
                            returnValue = cds.GetDownloadFileURL((string)commandParams[0]);
                            break;
                        case MapPointCommand.GetJobState:
                            returnValue = cds.GetJobState((string)commandParams[0]);
                            break;
                        case MapPointCommand.StartDownload:
                            returnValue = cds.StartDownload((DownloadSpecification)commandParams[0]);
                            break;
                        case MapPointCommand.StartUpload:
                            returnValue = cds.StartUpload((UploadSpecification)commandParams[0]);
                            break;
                        case MapPointCommand.UploadData:
                            returnValue = cds.UploadData((string)commandParams[0], (byte[])commandParams[1], (long)commandParams[2]);
                            break;
                    }

                    // If we've made it this far, the command succeeded, so no need to retry.
                    break;
                }
                catch (Exception ex)
                {
                    // We failed, but have we reached the max retry?
                    if (retryIndex >= this.retryAttempts)
                    {
                        // Yes, so since we cannot retry any more, the entire process has failed.
                        throw new ApplicationException("Failed to execute MapPoint command " + command.ToString() + " after " + retryIndex.ToString() + " attempts.", ex);
                    }
                    else
                    {
                        // We haven't reached our retry limit, so delay a few seconds, then let the outer loop retry the command.
                        System.Threading.Thread.Sleep(1000 * this.retryDelay);
                    }
                }
            }

            return returnValue;
        }

        #region IDispose implementation
        /// <summary>
        /// Perform cleanup of unmanaged resources
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.cds != null)
                {
                    this.cds.Dispose();
                    this.cds = null;
                }

            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion

    }
}